<?php
declare(strict_types=1);

namespace app\shop\service;

use app\admin\model\Admin;
use app\shop\enums\notify\NotifyMessageType;
use app\shop\library\ShopHelper;
use app\shop\library\ShopLogHelper;
use app\shop\library\ShopWorkerHelper;
use app\shop\model\notify\NotifyConfigModel;
use app\shop\model\notify\NotifyModel;
use think\db\exception\DbException;
use think\facade\Cache;
use Throwable;

class NotifyService
{
    public static string $adminNotifySuffix = '|joyshop_admin';
    public static string $cacheHashKey      = 'notify_unread_count';
    private object       $cacheHandle;

    public function __construct()
    {
        $this->cacheHandle = Cache::store('redis')->handler();
    }

    /**
     * 消息推送
     * @param string $messageType
     * @param array  $data
     * @return void
     */
    public function send(string $messageType, array $data): void
    {
        $notifyConfig = NotifyConfigModel::where('message_type', $messageType)->findOrEmpty();

        $message         = '收到一条新的消息，请及时处理！';
        $notifyAudioPath = '';
        $messageData     = [];

        if ($notifyConfig->isEmpty()) {
            ShopLogHelper::error('消息类型错误，消息推送失败！');
            return;
        }
        if ($notifyConfig->receiver_rule == 1) {
            // 白名单
            $adminIds = $notifyConfig->receiver_ids;
        } else {
            // 黑名单
            $allAdminIds = Admin::where('status', 1)->column('id');
            $adminIds    = array_diff($allAdminIds, $notifyConfig->receiver_ids);
        }

        $audioPath = $notifyConfig->audio_path;
        if (file_exists(public_path() . $audioPath)) {
            $notifyAudioPath = $audioPath;
        }

        $messageMap = NotifyMessageType::getAllDescriptions();
        $orderNo    = $data['order_no'] ?? '';
        if ($messageType == NotifyMessageType::ORDER) {
            $message     = '来新订单了，请及时处理！';
            $message     .= "订单号：$orderNo";
            $messageData = ['order_no' => $orderNo];
        }
        if ($messageType == NotifyMessageType::ORDER_REFUND) {
            $message     = '收到售后申请，请及时处理！';
            $message     .= "订单号：$orderNo";
            $messageData = ['order_no' => $orderNo];
        }
        if ($messageType == NotifyMessageType::ORDER_CANCEL) {
            $message     = '收到订单取消申请，请及时处理！';
            $message     .= "订单号：$orderNo";
            $messageData = ['order_no' => $orderNo];
        }
        if ($messageType == NotifyMessageType::ORDER_EXPORT) {
            $message     = "订单导出任务已完成，请及时处理！";
            $message     .= "文件名称：{$data['name']}";
            $adminIds    = [$data['admin_id']];
            $messageData = ['name' => $data['name']];
        }
        if ($messageType == NotifyMessageType::LOW_STOCK) {
            $message = '商品库存不足，请及时处理！';
            if (isset($data['goods'])) {
                $message .= "商品名称：{$data['goods']['name']}({$data['spec']})";
            } else {
                $message .= "商品名称: {$data['name']}";
            }
            $messageData = ['name' => $data['name']];
        }

        // 新增消息记录
        $notify    = NotifyModel::create([
            'receiver_id'  => implode(',', $adminIds),
            'reader_id'    => '',
            'message_type' => $messageType,
            'message'      => $message,
            'message_data' => ShopHelper::array_to_json($data),
        ]);
        $sendToIds = array_map([$this, 'getCacheAdminKey'], $adminIds);
        try {
            // 推送通知消息
            ShopWorkerHelper::instance()->send($sendToIds, 'notify', [
                'message' => [
                    'id'               => $notify->id,
                    'message'          => $message,
                    'message_data'     => $messageData,
                    'message_type'     => $messageType,
                    'message_type_map' => $messageMap,
                    'audio_path'       => $notifyAudioPath,
                ]
            ]);
            // 推送消息未读数记录
            $this->addUnreadRecords($adminIds);
        } catch (Throwable $e) {
            ShopLogHelper::error('消息推送失败！', [
                'message' => $e->getMessage(),
                'data'    => $data,
            ]);
        }
    }

    /**
     * 消息未读数计算
     * @param int|string $adminId
     * @return void
     * @throws DbException
     */
    public function unreadCount(int|string $adminId): void
    {
        $adminUid = $this->getCacheAdminKey($adminId);

        $unreadCount = NotifyModel::whereFindInSet('receiver_id', $adminId)
            ->whereRaw("NOT FIND_IN_SET($adminId, `reader_id`)")
            ->cache()
            ->count();
        $this->cacheHandle->hmset(self::$cacheHashKey, [$adminUid => $unreadCount]);

        ShopWorkerHelper::instance()->send($adminUid, 'notify', [
            'unread_count' => $unreadCount
        ]);

    }

    /**
     * 读取消息
     * @param int       $adminId  管理员ID
     * @param int|array $notifyId 消息ID
     * @return void
     */
    public function read(int $adminId, int|array $notifyId): void
    {
        try {
            $adminUid = $this->getCacheAdminKey($adminId);

            if (is_array($notifyId)) {
                $readCount = count($notifyId);
                $this->cacheHandle->hincrby(self::$cacheHashKey, $adminUid, -$readCount);
            } else {
                $this->cacheHandle->hincrby(self::$cacheHashKey, $adminUid, -1);
            }
            $newValue = $this->cacheHandle->hget(self::$cacheHashKey, $adminUid);
            if ($newValue < 0) {
                $this->cacheHandle->hset(self::$cacheHashKey, $adminUid, 0);
                $newValue = 0;
            }
            ShopWorkerHelper::instance()->send($adminUid, 'notify', [
                'unread_count' => $newValue
            ]);
        } catch (Throwable $e) {
            ShopLogHelper::error('消息读取失败', [
                'message' => $e->getMessage(),
                'file'    => $e->getFile(),
                'line'    => $e->getLine(),
            ]);
        }
    }

    /**
     * 添加新消息未读记录
     * @param array $adminIds
     * @return void
     * @throws DbException
     */
    public function addUnreadRecords(array $adminIds): void
    {
        $updateData = [];

        $cacheAdminIds = $this->getCacheAdminKey($adminIds);

        $cacheData = $this->cacheHandle->hgetAll(self::$cacheHashKey);

        if (empty($cacheData)) {
            $updateData = array_fill_keys($cacheAdminIds, 1);
        } else {
            foreach ($cacheAdminIds as $cacheAdminKey) {
                if (isset($cacheData[$cacheAdminKey])) {
                    $updateData[$cacheAdminKey] = intval($cacheData[$cacheAdminKey]) + 1;
                } else {
                    // 无缓存时从数据库获取
                    list($adminId) = explode('|', $cacheAdminKey);
                    $unreadCount = NotifyModel::whereFindInSet('receiver_id', $adminId)
                        ->whereRaw("NOT FIND_IN_SET($adminId, 'reader_id')")
                        ->count();

                    $updateData[$cacheAdminKey] = $unreadCount;
                }
            }
        }

        if (!empty($updateData)) {
            $this->cacheHandle->hMset(self::$cacheHashKey, $updateData);
        }

        // 发送消息通知
        foreach ($updateData as $key => $value) {
            ShopWorkerHelper::instance()->send($key, 'notify', [
                'unread_count' => $value
            ]);
        }
    }

    /*
     * 获取缓存key名
     * @param int|string|array $adminId
     * @return string|string[]
     */
    public function getCacheAdminKey(int|string|array $adminId): array|string
    {
        if (is_array($adminId)) {
            return array_map(function ($id) {
                return $id . self::$adminNotifySuffix;
            }, $adminId);
        }

        return $adminId . self::$adminNotifySuffix;
    }
}